package com.icw.utility;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import com.icw.basis.SpecialMap;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.StringUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializeFilter;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.icw.basis.DiyMap;

public abstract class JsonUtils {

	private static SerializerFeature[] serializerFeatures = new SerializerFeature[] { SerializerFeature.WriteMapNullValue };

	private static SerializeConfig config = null;

	public static SerializeConfig jsonObjectSerializeConfig() throws Exception {
		SerializeConfig config = new SerializeConfig();
		loadFilters(config);
		return config;
	};

	public static String serialize(Object o) throws Exception {
		return serialize(o, config = config != null ? config : jsonObjectSerializeConfig());
	};

	public static String serialize(Object o, SerializeConfig config) {
		if (!PoEntityHelper.isBasicType(o)) {
			o = serializeCircular(o);
		}
		return JSON.toJSONString(o, config, serializerFeatures);
	};

	public static <T> T deserialize(String input, Class<T> tclazz) {
		T res = JSON.parseObject(input, tclazz);
		if (tclazz == Map.class || tclazz == LinkedHashMap.class || tclazz == HashMap.class) {
			return tclazz.cast(PoEntityHelper.deepCopy(res));
		} else {
			return res;
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> T deserialize(Object obj, Class<T> class1) throws Exception {
		if (PoEntityHelper.isBasicType(obj)) {
			return (T) obj;
		} else {
			parseCircular(obj = PoEntityHelper.deepCopy(obj));
			if (null == class1 || class1 == Map.class || class1 == LinkedHashMap.class || class1 == HashMap.class) {
				return (T) obj;
			} else {
				Object json = JSON.toJSON(obj, config = config != null ? config : jsonObjectSerializeConfig());
				return JSON.toJavaObject((JSON) json, class1);
			}
		}
	};

	private static void loadFilters(SerializeConfig config) throws Exception {
		Set<Class<?>> entityClasses = IcwUtils.getAllEntities();
		entityClasses.add(SpecialMap.class);
		entityClasses.forEach(clazz -> config.addFilter(clazz, propertyFilter));
	}

	private static SerializeFilter propertyFilter = new PropertyFilter() {

		private Set<String> noNeedEntityProperty = SetUtils.unmodifiableSet(
//				"password"
		);

		@Override
		public boolean apply(Object object, String name, Object value) {
			if (value == null) {
				return true;
			} else if (noNeedEntityProperty.contains(name)) {
				return false;
			} else {
				return !PoEntityHelper.isLazyInstance(value);
			}
		}
	};

	private static void parseCircular(Object obj) throws Exception {
		String[] keys = PoEntityHelper.keyOf(obj);
		for (int i = 0; i < keys.length; i++) {
			parseCircular(obj, obj, keys[i], null, new ArrayList<Object>());
		}
	}

	private static void parseCircular(Object root, Object parent, String objkey, Object parentparent, Collection<Object> duplicateCache) throws Exception {
		Object obj = PoEntityHelper.readValueByProperty(parent, objkey);
		if (PoEntityHelper.isBasicType(obj)) {
			//
		} else if (duplicateCache.contains(obj)) {
			//
		} else if (Collection.class.isInstance(obj) || obj.getClass().isArray()) {
			Object[] objects = PoEntityHelper.toObjectArray(obj);
			for (int i = 0; i < objects.length; i++) {
				parseCircular(root, obj, i + "", parent, duplicateCache);
			}
		} else if (PoEntityHelper.readValueByProperty(obj, "$ref") != null) {
			String path = (String) PoEntityHelper.readValueByProperty(obj, "$ref");
			String firstChar = path.substring(0, 1);
			if (StringUtils.equals(path, "..")) {
				PoEntityHelper.writeValueByProperty(parent, objkey, parentparent);
			} else if (StringUtils.equals(firstChar, "@")) {
				PoEntityHelper.writeValueByProperty(parent, objkey, parent);
			} else if (StringUtils.equals(firstChar, "$")) {
				PoEntityHelper.writeValueByProperty(parent, objkey, PoEntityHelper.readValueByProperty(root, path.substring(1)));
			} else {
				throw new Exception("Unrecognize $ref=" + path);
			}
		} else {
			duplicateCache.add(parent);
			String[] keys = PoEntityHelper.keyOf(obj);
			for (String key : keys) {
				parseCircular(root, obj, key, parent, duplicateCache);
			}
		}
	}

	private static Object serializeCircular(Object root) {
		if (PoEntityHelper.isBasicType(root)) {
			return null;
		} else {
			DiyMap<Object, String> cache = new DiyMap<>();
			cache.put(root, "$");
			if (Collection.class.isInstance(root) || root.getClass().isArray()) {
				Object[] result = new Object[PoEntityHelper.toObjectArray(root).length];
				for (int i = 0; i < result.length; i++) {
					__serializeCircular(root, "$", i + "", cache, result);
				}
				return result;
			} else {
				Map<String, Object> result = mapObject();
				for (String key : PoEntityHelper.keyOf(root)) {
					__serializeCircular(root, "$", key, cache, result);
				}
				return result;
			}
		}
	}

	private static void __serializeCircular(Object parent, String base, String objkey, DiyMap<Object, String> cache, Object result) {
		Object obj = PoEntityHelper.readValueByProperty(parent, objkey);
		if (PoEntityHelper.isBasicType(obj)) {
			PoEntityHelper.writeValueByProperty(result, objkey, obj);
		} else if (PoEntityHelper.isLazyInstance(obj) && Collection.class.isInstance(obj)) {
			// skip
		} else if (cache.contains(obj)) {
			PoEntityHelper.writeValueByProperty(result, objkey, mapObject("$ref", cache.get(obj)));
		} else {
			String objrefstr = base + (Collection.class.isInstance(parent) || parent.getClass().isArray() ? ("[" + objkey + "]") : ("." + objkey));
			cache.put(obj, objrefstr);
			if (PoEntityHelper.isLazyInstance(obj)) {
				PoEntityHelper.writeValueByProperty(result, objkey, obj);
			} else if (Collection.class.isInstance(obj) || obj.getClass().isArray()) {
				Object[] nextResult = new Object[PoEntityHelper.toObjectArray(obj).length];
				PoEntityHelper.writeValueByProperty(result, objkey, nextResult);
				result = nextResult;
				for (int i = 0; i < nextResult.length; i++) {
					__serializeCircular(obj, objrefstr, i + "", cache, result);
				}
			} else {
				Object nextResult = mapObject();
				PoEntityHelper.writeValueByProperty(result, objkey, nextResult);
				result = nextResult;
				for (String key : PoEntityHelper.keyOf(obj)) {
					__serializeCircular(obj, objrefstr, key, cache, result);
				}
			}
		}
	}

	private static Map<String, Object> mapObject(Object... kvs) {
		Map<String, Object> res = new SpecialMap<>();
		res.putAll(IcwUtils.mapObject(kvs));
		return res;
	}
}
